// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// File method ordering: Methods in this file are in the same order as
// in download_item_impl.h, with the following exception: The public
// interface Start is placed in chronological order with the other
// (private) routines that together define a DownloadItem's state
// transitions as the download progresses.  See "Download progression
// cascade" later in this file.

// A regular DownloadItem (created for a download in this session of the
// browser) normally goes through the following states:
//      * Created (when download starts)
//      * Destination filename determined
//      * Entered into the history database.
//      * Made visible in the download shelf.
//      * All the data is saved.  Note that the actual data download occurs
//        in parallel with the above steps, but until those steps are
//        complete, the state of the data save will be ignored.
//      * Download file is renamed to its final name, and possibly
//        auto-opened.

#include "content/browser/download/download_item_impl.h"

#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/download/download_create_info.h"
#include "content/browser/download/download_file.h"
#include "content/browser/download/download_interrupt_reasons_impl.h"
#include "content/browser/download/download_item_impl_delegate.h"
#include "content/browser/download/download_net_log_parameters.h"
#include "content/browser/download/download_request_handle.h"
#include "content/browser/download/download_stats.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/download_danger_type.h"
#include "content/public/browser/download_interrupt_reasons.h"
#include "content/public/browser/download_url_parameters.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/common/referrer.h"
#include "net/log/net_log.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_parameters_callback.h"
#include "net/log/net_log_source.h"

namespace content {

namespace {

    bool DeleteDownloadedFile(const base::FilePath& path)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::FILE);

        // Make sure we only delete files.
        if (base::DirectoryExists(path))
            return true;
        return base::DeleteFile(path, false);
    }

    void DeleteDownloadedFileDone(
        base::WeakPtr<DownloadItemImpl> item,
        const base::Callback<void(bool)>& callback,
        bool success)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::UI);
        if (success && item.get())
            item->OnDownloadedFileRemoved();
        callback.Run(success);
    }

    // Wrapper around DownloadFile::Detach and DownloadFile::Cancel that
    // takes ownership of the DownloadFile and hence implicitly destroys it
    // at the end of the function.
    static base::FilePath DownloadFileDetach(
        std::unique_ptr<DownloadFile> download_file)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::FILE);
        base::FilePath full_path = download_file->FullPath();
        download_file->Detach();
        return full_path;
    }

    static base::FilePath MakeCopyOfDownloadFile(DownloadFile* download_file)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::FILE);
        base::FilePath temp_file_path;
        if (base::CreateTemporaryFile(&temp_file_path) && base::CopyFile(download_file->FullPath(), temp_file_path)) {
            return temp_file_path;
        } else {
            // Deletes the file at |temp_file_path|.
            if (!base::DirectoryExists(temp_file_path))
                base::DeleteFile(temp_file_path, false);
            temp_file_path.clear();
            return base::FilePath();
        }
    }

    static void DownloadFileCancel(std::unique_ptr<DownloadFile> download_file)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::FILE);
        download_file->Cancel();
    }

} // namespace

const uint32_t DownloadItem::kInvalidId = 0;

// The maximum number of attempts we will make to resume automatically.
const int DownloadItemImpl::kMaxAutoResumeAttempts = 5;

// Constructor for reading from the history service.
DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate,
    const std::string& guid,
    uint32_t download_id,
    const base::FilePath& current_path,
    const base::FilePath& target_path,
    const std::vector<GURL>& url_chain,
    const GURL& referrer_url,
    const GURL& site_url,
    const GURL& tab_url,
    const GURL& tab_refererr_url,
    const std::string& mime_type,
    const std::string& original_mime_type,
    const base::Time& start_time,
    const base::Time& end_time,
    const std::string& etag,
    const std::string& last_modified,
    int64_t received_bytes,
    int64_t total_bytes,
    const std::string& hash,
    DownloadItem::DownloadState state,
    DownloadDangerType danger_type,
    DownloadInterruptReason interrupt_reason,
    bool opened,
    const net::NetLogWithSource& net_log)
    : guid_(base::ToUpperASCII(guid))
    , download_id_(download_id)
    , target_path_(target_path)
    , url_chain_(url_chain)
    , referrer_url_(referrer_url)
    , site_url_(site_url)
    , tab_url_(tab_url)
    , tab_referrer_url_(tab_refererr_url)
    , mime_type_(mime_type)
    , original_mime_type_(original_mime_type)
    , total_bytes_(total_bytes)
    , last_reason_(interrupt_reason)
    , start_tick_(base::TimeTicks())
    , state_(ExternalToInternalState(state))
    , danger_type_(danger_type)
    , start_time_(start_time)
    , end_time_(end_time)
    , delegate_(delegate)
    , opened_(opened)
    , current_path_(current_path)
    , received_bytes_(received_bytes)
    , all_data_saved_(state == COMPLETE)
    , hash_(hash)
    , last_modified_time_(last_modified)
    , etag_(etag)
    , net_log_(net_log)
    , weak_ptr_factory_(this)
{
    delegate_->Attach();
    DCHECK(state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL || state_ == CANCELLED_INTERNAL);
    DCHECK(base::IsValidGUID(guid_));
    Init(false /* not actively downloading */, SRC_HISTORY_IMPORT);
}

// Constructing for a regular download:
DownloadItemImpl::DownloadItemImpl(DownloadItemImplDelegate* delegate,
    uint32_t download_id,
    const DownloadCreateInfo& info,
    const net::NetLogWithSource& net_log)
    : guid_(base::ToUpperASCII(base::GenerateGUID()))
    , download_id_(download_id)
    , target_disposition_((info.save_info->prompt_for_save_location)
              ? TARGET_DISPOSITION_PROMPT
              : TARGET_DISPOSITION_OVERWRITE)
    , url_chain_(info.url_chain)
    , referrer_url_(info.referrer_url)
    , site_url_(info.site_url)
    , tab_url_(info.tab_url)
    , tab_referrer_url_(info.tab_referrer_url)
    , suggested_filename_(base::UTF16ToUTF8(info.save_info->suggested_name))
    , forced_file_path_(info.save_info->file_path)
    , transition_type_(info.transition_type ? info.transition_type.value()
                                            : ui::PAGE_TRANSITION_LINK)
    , has_user_gesture_(info.has_user_gesture)
    , content_disposition_(info.content_disposition)
    , mime_type_(info.mime_type)
    , original_mime_type_(info.original_mime_type)
    , remote_address_(info.remote_address)
    , total_bytes_(info.total_bytes)
    , last_reason_(info.result)
    , start_tick_(base::TimeTicks::Now())
    , state_(INITIAL_INTERNAL)
    , start_time_(info.start_time)
    , delegate_(delegate)
    , is_temporary_(!info.save_info->file_path.empty())
    , last_modified_time_(info.last_modified)
    , etag_(info.etag)
    , net_log_(net_log)
    , weak_ptr_factory_(this)
{
    delegate_->Attach();
    Init(true /* actively downloading */, SRC_ACTIVE_DOWNLOAD);

    // Link the event sources.
    net_log_.AddEvent(
        net::NetLogEventType::DOWNLOAD_URL_REQUEST,
        info.request_net_log.source().ToEventParametersCallback());

    info.request_net_log.AddEvent(
        net::NetLogEventType::DOWNLOAD_STARTED,
        net_log_.source().ToEventParametersCallback());
}

// Constructing for the "Save Page As..." feature:
DownloadItemImpl::DownloadItemImpl(
    DownloadItemImplDelegate* delegate,
    uint32_t download_id,
    const base::FilePath& path,
    const GURL& url,
    const std::string& mime_type,
    std::unique_ptr<DownloadRequestHandleInterface> request_handle,
    const net::NetLogWithSource& net_log)
    : is_save_package_download_(true)
    , request_handle_(std::move(request_handle))
    , guid_(base::ToUpperASCII(base::GenerateGUID()))
    , download_id_(download_id)
    , target_path_(path)
    , url_chain_(1, url)
    , mime_type_(mime_type)
    , original_mime_type_(mime_type)
    , start_tick_(base::TimeTicks::Now())
    , state_(IN_PROGRESS_INTERNAL)
    , start_time_(base::Time::Now())
    , delegate_(delegate)
    , current_path_(path)
    , net_log_(net_log)
    , weak_ptr_factory_(this)
{
    delegate_->Attach();
    Init(true /* actively downloading */, SRC_SAVE_PAGE_AS);
}

DownloadItemImpl::~DownloadItemImpl()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    // Should always have been nuked before now, at worst in
    // DownloadManager shutdown.
    DCHECK(!download_file_.get());

    for (auto& observer : observers_)
        observer.OnDownloadDestroyed(this);
    delegate_->AssertStateConsistent(this);
    delegate_->Detach();
}

void DownloadItemImpl::AddObserver(Observer* observer)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    observers_.AddObserver(observer);
}

void DownloadItemImpl::RemoveObserver(Observer* observer)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    observers_.RemoveObserver(observer);
}

void DownloadItemImpl::UpdateObservers()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DVLOG(20) << __func__ << "()";

    for (auto& observer : observers_)
        observer.OnDownloadUpdated(this);
}

void DownloadItemImpl::ValidateDangerousDownload()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(!IsDone());
    DCHECK(IsDangerous());

    DVLOG(20) << __func__ << "() download=" << DebugString(true);

    if (IsDone() || !IsDangerous())
        return;

    RecordDangerousDownloadAccept(GetDangerType(),
        GetTargetFilePath());

    danger_type_ = DOWNLOAD_DANGER_TYPE_USER_VALIDATED;

    net_log_.AddEvent(
        net::NetLogEventType::DOWNLOAD_ITEM_SAFETY_STATE_UPDATED,
        base::Bind(&ItemCheckedNetLogCallback, GetDangerType()));

    UpdateObservers(); // TODO(asanka): This is potentially unsafe. The download
        // may not be in a consistent state or around at all after
        // invoking observers. http://crbug.com/586610

    MaybeCompleteDownload();
}

void DownloadItemImpl::StealDangerousDownload(
    bool delete_file_afterward,
    const AcquireFileCallback& callback)
{
    DVLOG(20) << __func__ << "() download = " << DebugString(true);
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(IsDangerous());
    DCHECK(AllDataSaved());

    if (delete_file_afterward) {
        if (download_file_) {
            BrowserThread::PostTaskAndReplyWithResult(
                BrowserThread::FILE, FROM_HERE,
                base::Bind(&DownloadFileDetach, base::Passed(&download_file_)),
                callback);
        } else {
            callback.Run(current_path_);
        }
        current_path_.clear();
        Remove();
        // Download item has now been deleted.
    } else if (download_file_) {
        BrowserThread::PostTaskAndReplyWithResult(
            BrowserThread::FILE, FROM_HERE,
            base::Bind(&MakeCopyOfDownloadFile, download_file_.get()), callback);
    } else {
        callback.Run(current_path_);
    }
}

void DownloadItemImpl::Pause()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    // Ignore irrelevant states.
    if (is_paused_)
        return;

    switch (state_) {
    case CANCELLED_INTERNAL:
    case COMPLETE_INTERNAL:
    case COMPLETING_INTERNAL:
    case INITIAL_INTERNAL:
    case INTERRUPTED_INTERNAL:
    case INTERRUPTED_TARGET_PENDING_INTERNAL:
    case RESUMING_INTERNAL:
        // No active request.
        // TODO(asanka): In the case of RESUMING_INTERNAL, consider setting
        // is_paused_ even if there's no request currently associated with this
        // DII. When a request is assigned (due to a resumption, for example) we
        // can honor the is_paused_ setting.
        return;

    case IN_PROGRESS_INTERNAL:
    case TARGET_PENDING_INTERNAL:
        request_handle_->PauseRequest();
        is_paused_ = true;
        UpdateObservers();
        return;

    case MAX_DOWNLOAD_INTERNAL_STATE:
    case TARGET_RESOLVED_INTERNAL:
        NOTREACHED();
    }
}

void DownloadItemImpl::Resume()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DVLOG(20) << __func__ << "() download = " << DebugString(true);
    switch (state_) {
    case CANCELLED_INTERNAL: // Nothing to resume.
    case COMPLETE_INTERNAL:
    case COMPLETING_INTERNAL:
    case INITIAL_INTERNAL:
    case INTERRUPTED_TARGET_PENDING_INTERNAL:
    case RESUMING_INTERNAL: // Resumption in progress.
        return;

    case TARGET_PENDING_INTERNAL:
    case IN_PROGRESS_INTERNAL:
        if (!is_paused_)
            return;
        request_handle_->ResumeRequest();
        is_paused_ = false;
        UpdateObservers();
        return;

    case INTERRUPTED_INTERNAL:
        auto_resume_count_ = 0; // User input resets the counter.
        ResumeInterruptedDownload(ResumptionRequestSource::USER);
        UpdateObservers();
        return;

    case MAX_DOWNLOAD_INTERNAL_STATE:
    case TARGET_RESOLVED_INTERNAL:
        NOTREACHED();
    }
}

void DownloadItemImpl::Cancel(bool user_cancel)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DVLOG(20) << __func__ << "() download = " << DebugString(true);
    InterruptAndDiscardPartialState(
        user_cancel ? DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
                    : DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN);
    UpdateObservers();
}

void DownloadItemImpl::Remove()
{
    DVLOG(20) << __func__ << "() download = " << DebugString(true);
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    delegate_->AssertStateConsistent(this);
    InterruptAndDiscardPartialState(DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
    UpdateObservers();
    delegate_->AssertStateConsistent(this);

    NotifyRemoved();
    delegate_->DownloadRemoved(this);
    // We have now been deleted.
}

void DownloadItemImpl::OpenDownload()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    if (!IsDone()) {
        // We don't honor the open_when_complete_ flag for temporary
        // downloads. Don't set it because it shows up in the UI.
        if (!IsTemporary())
            open_when_complete_ = !open_when_complete_;
        return;
    }

    if (state_ != COMPLETE_INTERNAL || file_externally_removed_)
        return;

    // Ideally, we want to detect errors in opening and report them, but we
    // don't generally have the proper interface for that to the external
    // program that opens the file.  So instead we spawn a check to update
    // the UI if the file has been deleted in parallel with the open.
    delegate_->CheckForFileRemoval(this);
    RecordOpen(GetEndTime(), !GetOpened());
    opened_ = true;
    for (auto& observer : observers_)
        observer.OnDownloadOpened(this);
    delegate_->OpenDownload(this);
}

void DownloadItemImpl::ShowDownloadInShell()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    delegate_->ShowDownloadInShell(this);
}

uint32_t DownloadItemImpl::GetId() const
{
    return download_id_;
}

const std::string& DownloadItemImpl::GetGuid() const
{
    return guid_;
}

DownloadItem::DownloadState DownloadItemImpl::GetState() const
{
    return InternalToExternalState(state_);
}

DownloadInterruptReason DownloadItemImpl::GetLastReason() const
{
    return last_reason_;
}

bool DownloadItemImpl::IsPaused() const
{
    return is_paused_;
}

bool DownloadItemImpl::IsTemporary() const
{
    return is_temporary_;
}

bool DownloadItemImpl::CanResume() const
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    switch (state_) {
    case INITIAL_INTERNAL:
    case COMPLETING_INTERNAL:
    case COMPLETE_INTERNAL:
    case CANCELLED_INTERNAL:
    case RESUMING_INTERNAL:
    case INTERRUPTED_TARGET_PENDING_INTERNAL:
        return false;

    case TARGET_PENDING_INTERNAL:
    case TARGET_RESOLVED_INTERNAL:
    case IN_PROGRESS_INTERNAL:
        return is_paused_;

    case INTERRUPTED_INTERNAL: {
        ResumeMode resume_mode = GetResumeMode();
        // Only allow Resume() calls if the resumption mode requires a user
        // action.
        return resume_mode == RESUME_MODE_USER_RESTART || resume_mode == RESUME_MODE_USER_CONTINUE;
    }

    case MAX_DOWNLOAD_INTERNAL_STATE:
        NOTREACHED();
    }
    return false;
}

bool DownloadItemImpl::IsDone() const
{
    switch (state_) {
    case INITIAL_INTERNAL:
    case COMPLETING_INTERNAL:
    case RESUMING_INTERNAL:
    case TARGET_PENDING_INTERNAL:
    case INTERRUPTED_TARGET_PENDING_INTERNAL:
    case TARGET_RESOLVED_INTERNAL:
    case IN_PROGRESS_INTERNAL:
        return false;

    case COMPLETE_INTERNAL:
    case CANCELLED_INTERNAL:
        return true;

    case INTERRUPTED_INTERNAL:
        return !CanResume();

    case MAX_DOWNLOAD_INTERNAL_STATE:
        NOTREACHED();
    }
    return false;
}

const GURL& DownloadItemImpl::GetURL() const
{
    return url_chain_.empty() ? GURL::EmptyGURL() : url_chain_.back();
}

const std::vector<GURL>& DownloadItemImpl::GetUrlChain() const
{
    return url_chain_;
}

const GURL& DownloadItemImpl::GetOriginalUrl() const
{
    // Be careful about taking the front() of possibly-empty vectors!
    // http://crbug.com/190096
    return url_chain_.empty() ? GURL::EmptyGURL() : url_chain_.front();
}

const GURL& DownloadItemImpl::GetReferrerUrl() const
{
    return referrer_url_;
}

const GURL& DownloadItemImpl::GetSiteUrl() const
{
    return site_url_;
}

const GURL& DownloadItemImpl::GetTabUrl() const
{
    return tab_url_;
}

const GURL& DownloadItemImpl::GetTabReferrerUrl() const
{
    return tab_referrer_url_;
}

std::string DownloadItemImpl::GetSuggestedFilename() const
{
    return suggested_filename_;
}

std::string DownloadItemImpl::GetContentDisposition() const
{
    return content_disposition_;
}

std::string DownloadItemImpl::GetMimeType() const
{
    return mime_type_;
}

std::string DownloadItemImpl::GetOriginalMimeType() const
{
    return original_mime_type_;
}

std::string DownloadItemImpl::GetRemoteAddress() const
{
    return remote_address_;
}

bool DownloadItemImpl::HasUserGesture() const
{
    return has_user_gesture_;
};

ui::PageTransition DownloadItemImpl::GetTransitionType() const
{
    return transition_type_;
};

const std::string& DownloadItemImpl::GetLastModifiedTime() const
{
    return last_modified_time_;
}

const std::string& DownloadItemImpl::GetETag() const
{
    return etag_;
}

bool DownloadItemImpl::IsSavePackageDownload() const
{
    return is_save_package_download_;
}

const base::FilePath& DownloadItemImpl::GetFullPath() const
{
    return current_path_;
}

const base::FilePath& DownloadItemImpl::GetTargetFilePath() const
{
    return target_path_;
}

const base::FilePath& DownloadItemImpl::GetForcedFilePath() const
{
    // TODO(asanka): Get rid of GetForcedFilePath(). We should instead just
    // require that clients respect GetTargetFilePath() if it is already set.
    return forced_file_path_;
}

base::FilePath DownloadItemImpl::GetFileNameToReportUser() const
{
    if (!display_name_.empty())
        return display_name_;
    return target_path_.BaseName();
}

DownloadItem::TargetDisposition DownloadItemImpl::GetTargetDisposition() const
{
    return target_disposition_;
}

const std::string& DownloadItemImpl::GetHash() const
{
    return hash_;
}

bool DownloadItemImpl::GetFileExternallyRemoved() const
{
    return file_externally_removed_;
}

void DownloadItemImpl::DeleteFile(const base::Callback<void(bool)>& callback)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    if (GetState() != DownloadItem::COMPLETE) {
        // Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved.
        BrowserThread::PostTask(
            BrowserThread::UI, FROM_HERE,
            base::Bind(&DeleteDownloadedFileDone,
                base::WeakPtr<DownloadItemImpl>(), callback, false));
        return;
    }
    if (current_path_.empty() || file_externally_removed_) {
        // Pass a null WeakPtr so it doesn't call OnDownloadedFileRemoved.
        BrowserThread::PostTask(
            BrowserThread::UI, FROM_HERE,
            base::Bind(&DeleteDownloadedFileDone,
                base::WeakPtr<DownloadItemImpl>(), callback, true));
        return;
    }
    BrowserThread::PostTaskAndReplyWithResult(
        BrowserThread::FILE, FROM_HERE,
        base::Bind(&DeleteDownloadedFile, current_path_),
        base::Bind(&DeleteDownloadedFileDone,
            weak_ptr_factory_.GetWeakPtr(), callback));
}

bool DownloadItemImpl::IsDangerous() const
{
    return (danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || danger_type_ == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED);
}

DownloadDangerType DownloadItemImpl::GetDangerType() const
{
    return danger_type_;
}

bool DownloadItemImpl::TimeRemaining(base::TimeDelta* remaining) const
{
    if (total_bytes_ <= 0)
        return false; // We never received the content_length for this download.

    int64_t speed = CurrentSpeed();
    if (speed == 0)
        return false;

    *remaining = base::TimeDelta::FromSeconds(
        (total_bytes_ - received_bytes_) / speed);
    return true;
}

int64_t DownloadItemImpl::CurrentSpeed() const
{
    if (is_paused_)
        return 0;
    return bytes_per_sec_;
}

int DownloadItemImpl::PercentComplete() const
{
    // If the delegate is delaying completion of the download, then we have no
    // idea how long it will take.
    if (delegate_delayed_complete_ || total_bytes_ <= 0)
        return -1;

    return static_cast<int>(received_bytes_ * 100.0 / total_bytes_);
}

bool DownloadItemImpl::AllDataSaved() const
{
    return all_data_saved_;
}

int64_t DownloadItemImpl::GetTotalBytes() const
{
    return total_bytes_;
}

int64_t DownloadItemImpl::GetReceivedBytes() const
{
    return received_bytes_;
}

base::Time DownloadItemImpl::GetStartTime() const
{
    return start_time_;
}

base::Time DownloadItemImpl::GetEndTime() const
{
    return end_time_;
}

bool DownloadItemImpl::CanShowInFolder()
{
    // A download can be shown in the folder if the downloaded file is in a known
    // location.
    return CanOpenDownload() && !GetFullPath().empty();
}

bool DownloadItemImpl::CanOpenDownload()
{
    // We can open the file or mark it for opening on completion if the download
    // is expected to complete successfully. Exclude temporary downloads, since
    // they aren't owned by the download system.
    const bool is_complete = GetState() == DownloadItem::COMPLETE;
    return (!IsDone() || is_complete) && !IsTemporary() && !file_externally_removed_;
}

bool DownloadItemImpl::ShouldOpenFileBasedOnExtension()
{
    return delegate_->ShouldOpenFileBasedOnExtension(GetTargetFilePath());
}

bool DownloadItemImpl::GetOpenWhenComplete() const
{
    return open_when_complete_;
}

bool DownloadItemImpl::GetAutoOpened()
{
    return auto_opened_;
}

bool DownloadItemImpl::GetOpened() const
{
    return opened_;
}

BrowserContext* DownloadItemImpl::GetBrowserContext() const
{
    return delegate_->GetBrowserContext();
}

WebContents* DownloadItemImpl::GetWebContents() const
{
    // TODO(rdsmith): Remove null check after removing GetWebContents() from
    // paths that might be used by DownloadItems created from history import.
    // Currently such items have null request_handle_s, where other items
    // (regular and SavePackage downloads) have actual objects off the pointer.
    if (request_handle_)
        return request_handle_->GetWebContents();
    return NULL;
}

void DownloadItemImpl::OnContentCheckCompleted(DownloadDangerType danger_type)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(AllDataSaved());

    // Danger type is only allowed to be set on an active download after all data
    // has been saved. This excludes all other states. In particular,
    // OnContentCheckCompleted() isn't allowed on an INTERRUPTED download since
    // such an interruption would need to happen between OnAllDataSaved() and
    // OnContentCheckCompleted() during which no disk or network activity
    // should've taken place.
    DCHECK_EQ(state_, IN_PROGRESS_INTERNAL);
    DVLOG(20) << __func__ << "() danger_type=" << danger_type
              << " download=" << DebugString(true);
    SetDangerType(danger_type);
    UpdateObservers();
}

void DownloadItemImpl::SetOpenWhenComplete(bool open)
{
    open_when_complete_ = open;
}

void DownloadItemImpl::SetOpened(bool opened)
{
    opened_ = opened;
}

void DownloadItemImpl::SetDisplayName(const base::FilePath& name)
{
    display_name_ = name;
}

std::string DownloadItemImpl::DebugString(bool verbose) const
{
    std::string description = base::StringPrintf("{ id = %d"
                                                 " state = %s",
        download_id_,
        DebugDownloadStateString(state_));

    // Construct a string of the URL chain.
    std::string url_list("<none>");
    if (!url_chain_.empty()) {
        std::vector<GURL>::const_iterator iter = url_chain_.begin();
        std::vector<GURL>::const_iterator last = url_chain_.end();
        url_list = (*iter).is_valid() ? (*iter).spec() : "<invalid>";
        ++iter;
        for (; verbose && (iter != last); ++iter) {
            url_list += " ->\n\t";
            const GURL& next_url = *iter;
            url_list += next_url.is_valid() ? next_url.spec() : "<invalid>";
        }
    }

    if (verbose) {
        description += base::StringPrintf(
            " total = %" PRId64 " received = %" PRId64
            " reason = %s"
            " paused = %c"
            " resume_mode = %s"
            " auto_resume_count = %d"
            " danger = %d"
            " all_data_saved = %c"
            " last_modified = '%s'"
            " etag = '%s'"
            " has_download_file = %s"
            " url_chain = \n\t\"%s\"\n\t"
            " current_path = \"%" PRFilePath
            "\"\n\t"
            " target_path = \"%" PRFilePath
            "\""
            " referrer = \"%s\""
            " site_url = \"%s\"",
            GetTotalBytes(), GetReceivedBytes(),
            DownloadInterruptReasonToString(last_reason_).c_str(),
            IsPaused() ? 'T' : 'F', DebugResumeModeString(GetResumeMode()),
            auto_resume_count_, GetDangerType(), AllDataSaved() ? 'T' : 'F',
            GetLastModifiedTime().c_str(), GetETag().c_str(),
            download_file_.get() ? "true" : "false", url_list.c_str(),
            GetFullPath().value().c_str(), GetTargetFilePath().value().c_str(),
            GetReferrerUrl().spec().c_str(), GetSiteUrl().spec().c_str());
    } else {
        description += base::StringPrintf(" url = \"%s\"", url_list.c_str());
    }

    description += " }";

    return description;
}

DownloadItemImpl::ResumeMode DownloadItemImpl::GetResumeMode() const
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    // Only support resumption for HTTP(S).
    if (!GetURL().SchemeIsHTTPOrHTTPS())
        return RESUME_MODE_INVALID;

    // We can't continue without a handle on the intermediate file.
    // We also can't continue if we don't have some verifier to make sure
    // we're getting the same file.
    bool restart_required = (current_path_.empty() || (etag_.empty() && last_modified_time_.empty()));

    // We won't auto-restart if we've used up our attempts or the
    // download has been paused by user action.
    bool user_action_required = (auto_resume_count_ >= kMaxAutoResumeAttempts || is_paused_);

    switch (last_reason_) {
    case DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR:
    case DOWNLOAD_INTERRUPT_REASON_NETWORK_TIMEOUT:
        break;

    case DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE:
        // The server disagreed with the file offset that we sent.

    case DOWNLOAD_INTERRUPT_REASON_FILE_HASH_MISMATCH:
        // The file on disk was found to not match the expected hash. Discard and
        // start from beginning.

    case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT:
        // The [possibly persisted] file offset disagreed with the file on disk.

        // The intermediate stub is not usable and the server is responding. Hence
        // retrying the request from the beginning is likely to work.
        restart_required = true;
        break;

    case DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED:
    case DOWNLOAD_INTERRUPT_REASON_NETWORK_DISCONNECTED:
    case DOWNLOAD_INTERRUPT_REASON_NETWORK_SERVER_DOWN:
    case DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED:
    case DOWNLOAD_INTERRUPT_REASON_SERVER_UNREACHABLE:
    case DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN:
    case DOWNLOAD_INTERRUPT_REASON_CRASH:
        // It is not clear whether attempting a resumption is acceptable at this
        // time or whether it would work at all. Hence allow the user to retry the
        // download manually.
        user_action_required = true;
        break;

    case DOWNLOAD_INTERRUPT_REASON_FILE_NO_SPACE:
        // There was no space. Require user interaction so that the user may, for
        // example, choose a different location to store the file. Or they may
        // free up some space on the targret device and retry. But try to reuse
        // the partial stub.
        user_action_required = true;
        break;

    case DOWNLOAD_INTERRUPT_REASON_FILE_FAILED:
    case DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED:
    case DOWNLOAD_INTERRUPT_REASON_FILE_NAME_TOO_LONG:
    case DOWNLOAD_INTERRUPT_REASON_FILE_TOO_LARGE:
        // Assume the partial stub is unusable. Also it may not be possible to
        // restart immediately.
        user_action_required = true;
        restart_required = true;
        break;

    case DOWNLOAD_INTERRUPT_REASON_NONE:
    case DOWNLOAD_INTERRUPT_REASON_NETWORK_INVALID_REQUEST:
    case DOWNLOAD_INTERRUPT_REASON_FILE_VIRUS_INFECTED:
    case DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT:
    case DOWNLOAD_INTERRUPT_REASON_USER_CANCELED:
    case DOWNLOAD_INTERRUPT_REASON_FILE_BLOCKED:
    case DOWNLOAD_INTERRUPT_REASON_FILE_SECURITY_CHECK_FAILED:
    case DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED:
    case DOWNLOAD_INTERRUPT_REASON_SERVER_CERT_PROBLEM:
    case DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN:
        // Unhandled.
        return RESUME_MODE_INVALID;
    }

    if (user_action_required && restart_required)
        return RESUME_MODE_USER_RESTART;

    if (restart_required)
        return RESUME_MODE_IMMEDIATE_RESTART;

    if (user_action_required)
        return RESUME_MODE_USER_CONTINUE;

    return RESUME_MODE_IMMEDIATE_CONTINUE;
}

void DownloadItemImpl::UpdateValidatorsOnResumption(
    const DownloadCreateInfo& new_create_info)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK_EQ(RESUMING_INTERNAL, state_);
    DCHECK(!new_create_info.url_chain.empty());

    // We are going to tack on any new redirects to our list of redirects.
    // When a download is resumed, the URL used for the resumption request is the
    // one at the end of the previous redirect chain. Tacking additional redirects
    // to the end of this chain ensures that:
    // - If the download needs to be resumed again, the ETag/Last-Modified headers
    //   will be used with the last server that sent them to us.
    // - The redirect chain contains all the servers that were involved in this
    //   download since the initial request, in order.
    std::vector<GURL>::const_iterator chain_iter = new_create_info.url_chain.begin();
    if (*chain_iter == url_chain_.back())
        ++chain_iter;

    // Record some stats. If the precondition failed (the server returned
    // HTTP_PRECONDITION_FAILED), then the download will automatically retried as
    // a full request rather than a partial. Full restarts clobber validators.
    int origin_state = 0;
    if (chain_iter != new_create_info.url_chain.end())
        origin_state |= ORIGIN_STATE_ON_RESUMPTION_ADDITIONAL_REDIRECTS;
    if (etag_ != new_create_info.etag || last_modified_time_ != new_create_info.last_modified)
        origin_state |= ORIGIN_STATE_ON_RESUMPTION_VALIDATORS_CHANGED;
    if (content_disposition_ != new_create_info.content_disposition)
        origin_state |= ORIGIN_STATE_ON_RESUMPTION_CONTENT_DISPOSITION_CHANGED;
    RecordOriginStateOnResumption(received_bytes_ != 0, origin_state);

    url_chain_.insert(
        url_chain_.end(), chain_iter, new_create_info.url_chain.end());
    etag_ = new_create_info.etag;
    last_modified_time_ = new_create_info.last_modified;
    content_disposition_ = new_create_info.content_disposition;

    // Don't update observers. This method is expected to be called just before a
    // DownloadFile is created and Start() is called. The observers will be
    // notified when the download transitions to the IN_PROGRESS state.
}

void DownloadItemImpl::NotifyRemoved()
{
    for (auto& observer : observers_)
        observer.OnDownloadRemoved(this);
}

void DownloadItemImpl::OnDownloadedFileRemoved()
{
    file_externally_removed_ = true;
    DVLOG(20) << __func__ << "() download=" << DebugString(true);
    UpdateObservers();
}

base::WeakPtr<DownloadDestinationObserver>
DownloadItemImpl::DestinationObserverAsWeakPtr()
{
    return weak_ptr_factory_.GetWeakPtr();
}

const net::NetLogWithSource& DownloadItemImpl::GetNetLogWithSource() const
{
    return net_log_;
}

void DownloadItemImpl::SetTotalBytes(int64_t total_bytes)
{
    total_bytes_ = total_bytes;
}

void DownloadItemImpl::OnAllDataSaved(
    int64_t total_bytes,
    std::unique_ptr<crypto::SecureHash> hash_state)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(!all_data_saved_);
    all_data_saved_ = true;
    SetTotalBytes(total_bytes);
    UpdateProgress(total_bytes, 0);
    SetHashState(std::move(hash_state));
    hash_state_.reset(); // No need to retain hash_state_ since we are done with
        // the download and don't expect to receive any more
        // data.

    DVLOG(20) << __func__ << "() download=" << DebugString(true);
    UpdateObservers();
}

void DownloadItemImpl::MarkAsComplete()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    DCHECK(all_data_saved_);
    end_time_ = base::Time::Now();
    TransitionTo(COMPLETE_INTERNAL);
    UpdateObservers();
}

void DownloadItemImpl::DestinationUpdate(int64_t bytes_so_far,
    int64_t bytes_per_sec)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    // If the download is in any other state we don't expect any
    // DownloadDestinationObserver callbacks. An interruption or a cancellation
    // results in a call to ReleaseDownloadFile which invalidates the weak
    // reference held by the DownloadFile and hence cuts off any pending
    // callbacks.
    DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL);

    // There must be no pending destination_error_.
    DCHECK_EQ(destination_error_, DOWNLOAD_INTERRUPT_REASON_NONE);

    DVLOG(20) << __func__ << "() so_far=" << bytes_so_far
              << " per_sec=" << bytes_per_sec
              << " download=" << DebugString(true);

    UpdateProgress(bytes_so_far, bytes_per_sec);
    if (net_log_.IsCapturing()) {
        net_log_.AddEvent(
            net::NetLogEventType::DOWNLOAD_ITEM_UPDATED,
            net::NetLog::Int64Callback("bytes_so_far", received_bytes_));
    }

    UpdateObservers();
}

void DownloadItemImpl::DestinationError(
    DownloadInterruptReason reason,
    int64_t bytes_so_far,
    std::unique_ptr<crypto::SecureHash> secure_hash)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    // If the download is in any other state we don't expect any
    // DownloadDestinationObserver callbacks. An interruption or a cancellation
    // results in a call to ReleaseDownloadFile which invalidates the weak
    // reference held by the DownloadFile and hence cuts off any pending
    // callbacks.
    DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL);
    DVLOG(20) << __func__
              << "() reason:" << DownloadInterruptReasonToString(reason);

    // Postpone recognition of this error until after file name determination
    // has completed and the intermediate file has been renamed to simplify
    // resumption conditions.
    if (state_ == TARGET_PENDING_INTERNAL) {
        received_bytes_ = bytes_so_far;
        hash_state_ = std::move(secure_hash);
        hash_.clear();
        destination_error_ = reason;
        return;
    }
    InterruptWithPartialState(bytes_so_far, std::move(secure_hash), reason);
    UpdateObservers();
}

void DownloadItemImpl::DestinationCompleted(
    int64_t total_bytes,
    std::unique_ptr<crypto::SecureHash> secure_hash)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    // If the download is in any other state we don't expect any
    // DownloadDestinationObserver callbacks. An interruption or a cancellation
    // results in a call to ReleaseDownloadFile which invalidates the weak
    // reference held by the DownloadFile and hence cuts off any pending
    // callbacks.
    DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL);
    DVLOG(20) << __func__ << "() download=" << DebugString(true);

    OnAllDataSaved(total_bytes, std::move(secure_hash));
    MaybeCompleteDownload();
}

// **** Download progression cascade

void DownloadItemImpl::Init(bool active,
    DownloadType download_type)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    if (active)
        RecordDownloadCount(START_COUNT);

    std::string file_name;
    if (download_type == SRC_HISTORY_IMPORT) {
        // target_path_ works for History and Save As versions.
        file_name = target_path_.AsUTF8Unsafe();
    } else {
        // See if it's set programmatically.
        file_name = forced_file_path_.AsUTF8Unsafe();
        // Possibly has a 'download' attribute for the anchor.
        if (file_name.empty())
            file_name = suggested_filename_;
        // From the URL file name.
        if (file_name.empty())
            file_name = GetURL().ExtractFileName();
    }

    net::NetLogParametersCallback active_data = base::Bind(&ItemActivatedNetLogCallback, this, download_type, &file_name);
    if (active) {
        net_log_.BeginEvent(net::NetLogEventType::DOWNLOAD_ITEM_ACTIVE,
            active_data);
    } else {
        net_log_.AddEvent(net::NetLogEventType::DOWNLOAD_ITEM_ACTIVE,
            active_data);
    }

    DVLOG(20) << __func__ << "() " << DebugString(true);
}

// We're starting the download.
void DownloadItemImpl::Start(
    std::unique_ptr<DownloadFile> file,
    std::unique_ptr<DownloadRequestHandleInterface> req_handle,
    const DownloadCreateInfo& new_create_info)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(!download_file_.get());
    DVLOG(20) << __func__ << "() this=" << DebugString(true);

    download_file_ = std::move(file);
    request_handle_ = std::move(req_handle);
    destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;

    if (state_ == CANCELLED_INTERNAL) {
        // The download was in the process of resuming when it was cancelled. Don't
        // proceed.
        ReleaseDownloadFile(true);
        if (request_handle_)
            request_handle_->CancelRequest();
        return;
    }

    // The state could be one of the following:
    //
    // INITIAL_INTERNAL: A normal download attempt.
    //
    // RESUMING_INTERNAL: A resumption attempt. May or may not have been
    //     successful.
    DCHECK(state_ == INITIAL_INTERNAL || state_ == RESUMING_INTERNAL);

    // If the state_ is INITIAL_INTERNAL, then the target path must be empty.
    DCHECK(state_ != INITIAL_INTERNAL || target_path_.empty());

    // If a resumption attempted failed, or if the download was DOA, then the
    // download should go back to being interrupted.
    if (new_create_info.result != DOWNLOAD_INTERRUPT_REASON_NONE) {
        DCHECK(!download_file_.get());

        // Download requests that are interrupted by Start() should result in a
        // DownloadCreateInfo with an intact DownloadSaveInfo.
        DCHECK(new_create_info.save_info);

        int64_t offset = new_create_info.save_info->offset;
        std::unique_ptr<crypto::SecureHash> hash_state = new_create_info.save_info->hash_state
            ? new_create_info.save_info->hash_state->Clone()
            : nullptr;

        // Interrupted downloads also need a target path.
        if (target_path_.empty()) {
            received_bytes_ = offset;
            hash_state_ = std::move(hash_state);
            hash_.clear();
            destination_error_ = new_create_info.result;
            TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
            DetermineDownloadTarget();
            return;
        }

        // Otherwise, this was a resumption attempt which ended with an
        // interruption. Continue with current target path.
        TransitionTo(TARGET_RESOLVED_INTERNAL);
        InterruptWithPartialState(
            offset, std::move(hash_state), new_create_info.result);
        UpdateObservers();
        return;
    }

    // Successful download start.
    DCHECK(download_file_.get());
    DCHECK(request_handle_.get());

    if (state_ == RESUMING_INTERNAL)
        UpdateValidatorsOnResumption(new_create_info);

    TransitionTo(TARGET_PENDING_INTERNAL);

    BrowserThread::PostTask(
        BrowserThread::FILE, FROM_HERE,
        base::Bind(&DownloadFile::Initialize,
            // Safe because we control download file lifetime.
            base::Unretained(download_file_.get()),
            base::Bind(&DownloadItemImpl::OnDownloadFileInitialized,
                weak_ptr_factory_.GetWeakPtr())));
}

void DownloadItemImpl::OnDownloadFileInitialized(
    DownloadInterruptReason result)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK_EQ(state_, TARGET_PENDING_INTERNAL);
    DVLOG(20) << __func__
              << "() result:" << DownloadInterruptReasonToString(result);
    if (result != DOWNLOAD_INTERRUPT_REASON_NONE) {
        // Whoops. That didn't work. Proceed as an interrupted download, but reset
        // the partial state. Currently, the partial stub cannot be recovered if the
        // download file initialization fails.
        received_bytes_ = 0;
        hash_state_.reset();
        hash_.clear();
        destination_error_ = result;
        TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
    }

    DetermineDownloadTarget();
}

void DownloadItemImpl::DetermineDownloadTarget()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DVLOG(20) << __func__ << "() " << DebugString(true);

    delegate_->DetermineDownloadTarget(
        this, base::Bind(&DownloadItemImpl::OnDownloadTargetDetermined, weak_ptr_factory_.GetWeakPtr()));
}

// Called by delegate_ when the download target path has been determined.
void DownloadItemImpl::OnDownloadTargetDetermined(
    const base::FilePath& target_path,
    TargetDisposition disposition,
    DownloadDangerType danger_type,
    const base::FilePath& intermediate_path)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == INTERRUPTED_TARGET_PENDING_INTERNAL);

    // If the |target_path| is empty, then we consider this download to be
    // canceled.
    if (target_path.empty()) {
        Cancel(true);
        return;
    }

    DVLOG(20) << __func__ << "() target_path:" << target_path.value()
              << " disposition:" << disposition << " danger_type:" << danger_type
              << " this:" << DebugString(true);

    target_path_ = target_path;
    target_disposition_ = disposition;
    SetDangerType(danger_type);

    // This was an interrupted download that was looking for a filename. Now that
    // it has one, transition to interrupted.
    if (state_ == INTERRUPTED_TARGET_PENDING_INTERNAL) {
        InterruptWithPartialState(
            received_bytes_, std::move(hash_state_), destination_error_);
        destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;
        UpdateObservers();
        return;
    }

    // We want the intermediate and target paths to refer to the same directory so
    // that they are both on the same device and subject to same
    // space/permission/availability constraints.
    DCHECK(intermediate_path.DirName() == target_path.DirName());

    // During resumption, we may choose to proceed with the same intermediate
    // file. No rename is necessary if our intermediate file already has the
    // correct name.
    //
    // The intermediate name may change from its original value during filename
    // determination on resumption, for example if the reason for the interruption
    // was the download target running out space, resulting in a user prompt.
    if (intermediate_path == current_path_) {
        OnDownloadRenamedToIntermediateName(DOWNLOAD_INTERRUPT_REASON_NONE,
            intermediate_path);
        return;
    }

    // Rename to intermediate name.
    // TODO(asanka): Skip this rename if AllDataSaved() is true. This avoids a
    //               spurious rename when we can just rename to the final
    //               filename. Unnecessary renames may cause bugs like
    //               http://crbug.com/74187.
    DCHECK(!is_save_package_download_);
    DCHECK(download_file_.get());
    DownloadFile::RenameCompletionCallback callback = base::Bind(&DownloadItemImpl::OnDownloadRenamedToIntermediateName,
        weak_ptr_factory_.GetWeakPtr());
    BrowserThread::PostTask(
        BrowserThread::FILE, FROM_HERE,
        base::Bind(&DownloadFile::RenameAndUniquify,
            // Safe because we control download file lifetime.
            base::Unretained(download_file_.get()),
            intermediate_path, callback));
}

void DownloadItemImpl::OnDownloadRenamedToIntermediateName(
    DownloadInterruptReason reason,
    const base::FilePath& full_path)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK_EQ(state_, TARGET_PENDING_INTERNAL);
    DVLOG(20) << __func__ << "() download=" << DebugString(true);

    TransitionTo(TARGET_RESOLVED_INTERNAL);

    // If the intermediate rename fails while there's also a destination_error_,
    // then the former is considered the critical error since it requires
    // discarding the partial state.
    if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) {
        // TODO(asanka): Even though the rename failed, it may still be possible to
        // recover the partial state from the 'before' name.
        InterruptAndDiscardPartialState(reason);
        UpdateObservers();
        return;
    }

    if (DOWNLOAD_INTERRUPT_REASON_NONE != destination_error_) {
        SetFullPath(full_path);
        InterruptWithPartialState(
            received_bytes_, std::move(hash_state_), destination_error_);
        destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;
        UpdateObservers();
        return;
    }

    SetFullPath(full_path);
    TransitionTo(IN_PROGRESS_INTERNAL);
    // TODO(asanka): Calling UpdateObservers() prior to MaybeCompleteDownload() is
    // not safe. The download could be in an underminate state after invoking
    // observers. http://crbug.com/586610
    UpdateObservers();
    MaybeCompleteDownload();
}

// When SavePackage downloads MHTML to GData (see
// SavePackageFilePickerChromeOS), GData calls MaybeCompleteDownload() like it
// does for non-SavePackage downloads, but SavePackage downloads never satisfy
// IsDownloadReadyForCompletion(). GDataDownloadObserver manually calls
// DownloadItem::UpdateObservers() when the upload completes so that SavePackage
// notices that the upload has completed and runs its normal Finish() pathway.
// MaybeCompleteDownload() is never the mechanism by which SavePackage completes
// downloads. SavePackage always uses its own Finish() to mark downloads
// complete.
void DownloadItemImpl::MaybeCompleteDownload()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(!is_save_package_download_);

    if (!IsDownloadReadyForCompletion(
            base::Bind(&DownloadItemImpl::MaybeCompleteDownload,
                weak_ptr_factory_.GetWeakPtr())))
        return;
    // Confirm we're in the proper set of states to be here; have all data, have a
    // history handle, (validated or safe).
    DCHECK_EQ(IN_PROGRESS_INTERNAL, state_);
    DCHECK(!IsDangerous());
    DCHECK(all_data_saved_);

    OnDownloadCompleting();
}

// Called by MaybeCompleteDownload() when it has determined that the download
// is ready for completion.
void DownloadItemImpl::OnDownloadCompleting()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    if (state_ != IN_PROGRESS_INTERNAL)
        return;

    DVLOG(20) << __func__ << "() " << DebugString(true);
    DCHECK(!GetTargetFilePath().empty());
    DCHECK(!IsDangerous());

    // TODO(rdsmith/benjhayden): Remove as part of SavePackage integration.
    if (is_save_package_download_) {
        // Avoid doing anything on the file thread; there's nothing we control
        // there.  Strictly speaking, this skips giving the embedder a chance to
        // open the download.  But on a save package download, there's no real
        // concept of opening.
        Completed();
        return;
    }

    DCHECK(download_file_.get());
    // Unilaterally rename; even if it already has the right name,
    // we need theannotation.
    DownloadFile::RenameCompletionCallback callback = base::Bind(&DownloadItemImpl::OnDownloadRenamedToFinalName,
        weak_ptr_factory_.GetWeakPtr());
    BrowserThread::PostTask(
        BrowserThread::FILE,
        FROM_HERE,
        base::Bind(&DownloadFile::RenameAndAnnotate,
            base::Unretained(download_file_.get()),
            GetTargetFilePath(),
            delegate_->GetApplicationClientIdForFileScanning(),
            GetURL(),
            GetReferrerUrl(),
            callback));
}

void DownloadItemImpl::OnDownloadRenamedToFinalName(
    DownloadInterruptReason reason,
    const base::FilePath& full_path)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK(!is_save_package_download_);

    // If a cancel or interrupt hit, we'll cancel the DownloadFile, which
    // will result in deleting the file on the file thread.  So we don't
    // care about the name having been changed.
    if (state_ != IN_PROGRESS_INTERNAL)
        return;

    DVLOG(20) << __func__ << "() full_path = \"" << full_path.value() << "\" "
              << DebugString(false);

    if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) {
        // Failure to perform the final rename is considered fatal. TODO(asanka): It
        // may not be, in which case we should figure out whether we can recover the
        // state.
        InterruptAndDiscardPartialState(reason);
        UpdateObservers();
        return;
    }

    DCHECK(target_path_ == full_path);

    if (full_path != current_path_) {
        // full_path is now the current and target file path.
        DCHECK(!full_path.empty());
        SetFullPath(full_path);
    }

    // Complete the download and release the DownloadFile.
    DCHECK(download_file_);
    ReleaseDownloadFile(false);

    // We're not completely done with the download item yet, but at this
    // point we're committed to complete the download.  Cancels (or Interrupts,
    // though it's not clear how they could happen) after this point will be
    // ignored.
    TransitionTo(COMPLETING_INTERNAL);

    if (delegate_->ShouldOpenDownload(
            this, base::Bind(&DownloadItemImpl::DelayedDownloadOpened, weak_ptr_factory_.GetWeakPtr()))) {
        Completed();
    } else {
        delegate_delayed_complete_ = true;
        UpdateObservers();
    }
}

void DownloadItemImpl::DelayedDownloadOpened(bool auto_opened)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    auto_opened_ = auto_opened;
    Completed();
}

void DownloadItemImpl::Completed()
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    DVLOG(20) << __func__ << "() " << DebugString(false);

    DCHECK(all_data_saved_);
    end_time_ = base::Time::Now();
    TransitionTo(COMPLETE_INTERNAL);
    RecordDownloadCompleted(start_tick_, received_bytes_);

    if (auto_opened_) {
        // If it was already handled by the delegate, do nothing.
    } else if (GetOpenWhenComplete() || ShouldOpenFileBasedOnExtension() || IsTemporary()) {
        // If the download is temporary, like in drag-and-drop, do not open it but
        // we still need to set it auto-opened so that it can be removed from the
        // download shelf.
        if (!IsTemporary())
            OpenDownload();

        auto_opened_ = true;
    }
    UpdateObservers();
}

// **** End of Download progression cascade

void DownloadItemImpl::InterruptAndDiscardPartialState(
    DownloadInterruptReason reason)
{
    InterruptWithPartialState(0, std::unique_ptr<crypto::SecureHash>(), reason);
}

void DownloadItemImpl::InterruptWithPartialState(
    int64_t bytes_so_far,
    std::unique_ptr<crypto::SecureHash> hash_state,
    DownloadInterruptReason reason)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, reason);
    DVLOG(20) << __func__
              << "() reason:" << DownloadInterruptReasonToString(reason)
              << " bytes_so_far:" << bytes_so_far
              << " hash_state:" << (hash_state ? "Valid" : "Invalid")
              << " this=" << DebugString(true);

    // Somewhat counter-intuitively, it is possible for us to receive an
    // interrupt after we've already been interrupted.  The generation of
    // interrupts from the file thread Renames and the generation of
    // interrupts from disk writes go through two different mechanisms (driven
    // by rename requests from UI thread and by write requests from IO thread,
    // respectively), and since we choose not to keep state on the File thread,
    // this is the place where the races collide.  It's also possible for
    // interrupts to race with cancels.
    switch (state_) {
    case CANCELLED_INTERNAL:
    // If the download is already cancelled, then there's no point in
    // transitioning out to interrupted.
    case COMPLETING_INTERNAL:
    case COMPLETE_INTERNAL:
        // Already complete.
        return;

    case INITIAL_INTERNAL:
    case MAX_DOWNLOAD_INTERNAL_STATE:
        NOTREACHED();
        return;

    case INTERRUPTED_TARGET_PENDING_INTERNAL:
    case IN_PROGRESS_INTERNAL:
    case TARGET_PENDING_INTERNAL:
    case TARGET_RESOLVED_INTERNAL:
        // last_reason_ needs to be set for GetResumeMode() to work.
        last_reason_ = reason;

        if (download_file_) {
            ResumeMode resume_mode = GetResumeMode();
            ReleaseDownloadFile(resume_mode != RESUME_MODE_IMMEDIATE_CONTINUE && resume_mode != RESUME_MODE_USER_CONTINUE);
        }
        break;

    case RESUMING_INTERNAL:
    case INTERRUPTED_INTERNAL:
        DCHECK(!download_file_);
        // The first non-cancel interrupt reason wins in cases where multiple
        // things go wrong.
        if (reason != DOWNLOAD_INTERRUPT_REASON_USER_CANCELED && reason != DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN)
            return;

        last_reason_ = reason;
        if (!current_path_.empty()) {
            // There is no download file and this is transitioning from INTERRUPTED
            // to CANCELLED. The intermediate file is no longer usable, and should
            // be deleted.
            BrowserThread::PostTask(
                BrowserThread::FILE, FROM_HERE,
                base::Bind(base::IgnoreResult(&DeleteDownloadedFile),
                    current_path_));
            current_path_.clear();
        }
        break;
    }

    // Reset all data saved, as even if we did save all the data we're going to go
    // through another round of downloading when we resume. There's a potential
    // problem here in the abstract, as if we did download all the data and then
    // run into a continuable error, on resumption we won't download any more
    // data.  However, a) there are currently no continuable errors that can occur
    // after we download all the data, and b) if there were, that would probably
    // simply result in a null range request, which would generate a
    // DestinationCompleted() notification from the DownloadFile, which would
    // behave properly with setting all_data_saved_ to false here.
    all_data_saved_ = false;

    if (current_path_.empty()) {
        hash_state_.reset();
        hash_.clear();
        received_bytes_ = 0;
    } else {
        UpdateProgress(bytes_so_far, 0);
        SetHashState(std::move(hash_state));
    }

    if (request_handle_)
        request_handle_->CancelRequest();

    if (reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED || reason == DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN) {
        if (IsDangerous()) {
            RecordDangerousDownloadDiscard(
                reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
                    ? DOWNLOAD_DISCARD_DUE_TO_USER_ACTION
                    : DOWNLOAD_DISCARD_DUE_TO_SHUTDOWN,
                GetDangerType(), GetTargetFilePath());
        }

        RecordDownloadCount(CANCELLED_COUNT);
        TransitionTo(CANCELLED_INTERNAL);
        return;
    }

    RecordDownloadInterrupted(reason, received_bytes_, total_bytes_);
    if (!GetWebContents())
        RecordDownloadCount(INTERRUPTED_WITHOUT_WEBCONTENTS);

    TransitionTo(INTERRUPTED_INTERNAL);
    AutoResumeIfValid();
}

void DownloadItemImpl::UpdateProgress(int64_t bytes_so_far,
    int64_t bytes_per_sec)
{
    received_bytes_ = bytes_so_far;
    bytes_per_sec_ = bytes_per_sec;

    // If we've received more data than we were expecting (bad server info?),
    // revert to 'unknown size mode'.
    if (received_bytes_ > total_bytes_)
        total_bytes_ = 0;
}

void DownloadItemImpl::SetHashState(
    std::unique_ptr<crypto::SecureHash> hash_state)
{
    hash_state_ = std::move(hash_state);
    if (!hash_state_) {
        hash_.clear();
        return;
    }

    std::unique_ptr<crypto::SecureHash> clone_of_hash_state(hash_state_->Clone());
    std::vector<char> hash_value(clone_of_hash_state->GetHashLength());
    clone_of_hash_state->Finish(&hash_value.front(), hash_value.size());
    hash_.assign(hash_value.begin(), hash_value.end());
}

void DownloadItemImpl::ReleaseDownloadFile(bool destroy_file)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DVLOG(20) << __func__ << "() destroy_file:" << destroy_file;

    if (destroy_file) {
        BrowserThread::PostTask(
            BrowserThread::FILE, FROM_HERE,
            // Will be deleted at end of task execution.
            base::Bind(&DownloadFileCancel, base::Passed(&download_file_)));
        // Avoid attempting to reuse the intermediate file by clearing out
        // current_path_.
        current_path_.clear();
    } else {
        BrowserThread::PostTask(
            BrowserThread::FILE,
            FROM_HERE,
            base::Bind(base::IgnoreResult(&DownloadFileDetach),
                // Will be deleted at end of task execution.
                base::Passed(&download_file_)));
    }
    // Don't accept any more messages from the DownloadFile, and null
    // out any previous "all data received".  This also breaks links to
    // other entities we've given out weak pointers to.
    weak_ptr_factory_.InvalidateWeakPtrs();
}

bool DownloadItemImpl::IsDownloadReadyForCompletion(
    const base::Closure& state_change_notification)
{
    // If the download hasn't progressed to the IN_PROGRESS state, then it's not
    // ready for completion.
    if (state_ != IN_PROGRESS_INTERNAL)
        return false;

    // If we don't have all the data, the download is not ready for
    // completion.
    if (!AllDataSaved())
        return false;

    // If the download is dangerous, but not yet validated, it's not ready for
    // completion.
    if (IsDangerous())
        return false;

    // Check for consistency before invoking delegate. Since there are no pending
    // target determination calls and the download is in progress, both the target
    // and current paths should be non-empty and they should point to the same
    // directory.
    DCHECK(!target_path_.empty());
    DCHECK(!current_path_.empty());
    DCHECK(target_path_.DirName() == current_path_.DirName());

    // Give the delegate a chance to hold up a stop sign.  It'll call
    // use back through the passed callback if it does and that state changes.
    if (!delegate_->ShouldCompleteDownload(this, state_change_notification))
        return false;

    return true;
}

void DownloadItemImpl::TransitionTo(DownloadInternalState new_state)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    if (state_ == new_state)
        return;

    DownloadInternalState old_state = state_;
    state_ = new_state;

    DCHECK(is_save_package_download_
            ? IsValidSavePackageStateTransition(old_state, new_state)
            : IsValidStateTransition(old_state, new_state))
        << "Invalid state transition from:" << DebugDownloadStateString(old_state)
        << " to:" << DebugDownloadStateString(new_state);

    switch (state_) {
    case INITIAL_INTERNAL:
        NOTREACHED();
        break;

    case TARGET_PENDING_INTERNAL:
    case TARGET_RESOLVED_INTERNAL:
    case INTERRUPTED_TARGET_PENDING_INTERNAL:
        break;

    case IN_PROGRESS_INTERNAL:
        DCHECK(!current_path_.empty()) << "Current output path must be known.";
        DCHECK(!target_path_.empty()) << "Target path must be known.";
        DCHECK(current_path_.DirName() == target_path_.DirName())
            << "Current output directory must match target directory.";
        DCHECK(download_file_) << "Output file must be owned by download item.";
        DCHECK(request_handle_) << "Download source must be active.";
        DCHECK(!is_paused_) << "At the time a download enters IN_PROGRESS state, "
                               "it must not be paused.";
        break;

    case COMPLETING_INTERNAL:
        DCHECK(all_data_saved_) << "All data must be saved prior to completion.";
        DCHECK(!download_file_)
            << "Download file must be released prior to completion.";
        DCHECK(!target_path_.empty()) << "Target path must be known.";
        DCHECK(current_path_ == target_path_)
            << "Current output path must match target path.";

        net_log_.AddEvent(
            net::NetLogEventType::DOWNLOAD_ITEM_COMPLETING,
            base::Bind(&ItemCompletingNetLogCallback, received_bytes_, &hash_));
        break;

    case COMPLETE_INTERNAL:
        net_log_.AddEvent(
            net::NetLogEventType::DOWNLOAD_ITEM_FINISHED,
            base::Bind(&ItemFinishedNetLogCallback, auto_opened_));
        break;

    case INTERRUPTED_INTERNAL:
        net_log_.AddEvent(net::NetLogEventType::DOWNLOAD_ITEM_INTERRUPTED,
            base::Bind(&ItemInterruptedNetLogCallback,
                last_reason_, received_bytes_));
        break;

    case RESUMING_INTERNAL:
        net_log_.AddEvent(net::NetLogEventType::DOWNLOAD_ITEM_RESUMED,
            base::Bind(&ItemResumingNetLogCallback, false,
                last_reason_, received_bytes_));
        break;

    case CANCELLED_INTERNAL:
        net_log_.AddEvent(
            net::NetLogEventType::DOWNLOAD_ITEM_CANCELED,
            base::Bind(&ItemCanceledNetLogCallback, received_bytes_));
        break;

    case MAX_DOWNLOAD_INTERNAL_STATE:
        NOTREACHED();
        break;
    }

    DVLOG(20) << __func__ << "() from:" << DebugDownloadStateString(old_state)
              << " to:" << DebugDownloadStateString(state_)
              << " this = " << DebugString(true);
    bool is_done = (state_ == COMPLETE_INTERNAL || state_ == INTERRUPTED_INTERNAL || state_ == RESUMING_INTERNAL || state_ == CANCELLED_INTERNAL);
    bool was_done = (old_state == COMPLETE_INTERNAL || old_state == INTERRUPTED_INTERNAL || old_state == RESUMING_INTERNAL || old_state == CANCELLED_INTERNAL);

    // Termination
    if (is_done && !was_done)
        net_log_.EndEvent(net::NetLogEventType::DOWNLOAD_ITEM_ACTIVE);

    // Resumption
    if (was_done && !is_done) {
        std::string file_name(target_path_.BaseName().AsUTF8Unsafe());
        net_log_.BeginEvent(net::NetLogEventType::DOWNLOAD_ITEM_ACTIVE,
            base::Bind(&ItemActivatedNetLogCallback, this,
                SRC_ACTIVE_DOWNLOAD, &file_name));
    }
}

void DownloadItemImpl::SetDangerType(DownloadDangerType danger_type)
{
    if (danger_type != danger_type_) {
        net_log_.AddEvent(
            net::NetLogEventType::DOWNLOAD_ITEM_SAFETY_STATE_UPDATED,
            base::Bind(&ItemCheckedNetLogCallback, danger_type));
    }
    // Only record the Malicious UMA stat if it's going from {not malicious} ->
    // {malicious}.
    if ((danger_type_ == DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS || danger_type_ == DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE || danger_type_ == DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT || danger_type_ == DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT) && (danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST || danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_URL || danger_type == DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT || danger_type == DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED)) {
        RecordMaliciousDownloadClassified(danger_type);
    }
    danger_type_ = danger_type;
}

void DownloadItemImpl::SetFullPath(const base::FilePath& new_path)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    DVLOG(20) << __func__ << "() new_path = \"" << new_path.value() << "\" "
              << DebugString(true);
    DCHECK(!new_path.empty());

    net_log_.AddEvent(
        net::NetLogEventType::DOWNLOAD_ITEM_RENAMED,
        base::Bind(&ItemRenamedNetLogCallback, &current_path_, &new_path));

    current_path_ = new_path;
}

void DownloadItemImpl::AutoResumeIfValid()
{
    DVLOG(20) << __func__ << "() " << DebugString(true);
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    ResumeMode mode = GetResumeMode();

    if (mode != RESUME_MODE_IMMEDIATE_RESTART && mode != RESUME_MODE_IMMEDIATE_CONTINUE) {
        return;
    }

    auto_resume_count_++;

    ResumeInterruptedDownload(ResumptionRequestSource::AUTOMATIC);
}

void DownloadItemImpl::ResumeInterruptedDownload(
    ResumptionRequestSource source)
{
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    // If we're not interrupted, ignore the request; our caller is drunk.
    if (state_ != INTERRUPTED_INTERNAL)
        return;

    // We are starting a new request. Shake off all pending operations.
    DCHECK(!download_file_);
    weak_ptr_factory_.InvalidateWeakPtrs();

    // Reset the appropriate state if restarting.
    ResumeMode mode = GetResumeMode();
    if (mode == RESUME_MODE_IMMEDIATE_RESTART || mode == RESUME_MODE_USER_RESTART) {
        received_bytes_ = 0;
        last_modified_time_.clear();
        etag_.clear();
        hash_.clear();
        hash_state_.reset();
    }

    StoragePartition* storage_partition = BrowserContext::GetStoragePartitionForSite(GetBrowserContext(),
        site_url_);

    // Avoid using the WebContents even if it's still around. Resumption requests
    // are consistently routed through the no-renderer code paths so that the
    // request will not be dropped if the WebContents (and by extension, the
    // associated renderer) goes away before a response is received.
    std::unique_ptr<DownloadUrlParameters> download_params(
        new DownloadUrlParameters(GetURL(),
            storage_partition->GetURLRequestContext()));
    download_params->set_file_path(GetFullPath());
    download_params->set_offset(GetReceivedBytes());
    download_params->set_last_modified(GetLastModifiedTime());
    download_params->set_etag(GetETag());
    download_params->set_hash_of_partial_file(hash_);
    download_params->set_hash_state(std::move(hash_state_));

    // Note that resumed downloads disallow redirects. Hence the referrer URL
    // (which is the contents of the Referer header for the last download request)
    // will only be sent to the URL returned by GetURL().
    download_params->set_referrer(
        Referrer(GetReferrerUrl(), blink::WebReferrerPolicyAlways));

    TransitionTo(RESUMING_INTERNAL);
    RecordDownloadSource(source == ResumptionRequestSource::USER
            ? INITIATED_BY_MANUAL_RESUMPTION
            : INITIATED_BY_AUTOMATIC_RESUMPTION);
    delegate_->ResumeInterruptedDownload(std::move(download_params), GetId());
    // Just in case we were interrupted while paused.
    is_paused_ = false;
}

// static
DownloadItem::DownloadState DownloadItemImpl::InternalToExternalState(
    DownloadInternalState internal_state)
{
    switch (internal_state) {
    case INITIAL_INTERNAL:
    case TARGET_PENDING_INTERNAL:
    case TARGET_RESOLVED_INTERNAL:
    case INTERRUPTED_TARGET_PENDING_INTERNAL:
    // TODO(asanka): Introduce an externally visible state to distinguish
    // between the above states and IN_PROGRESS_INTERNAL. The latter (the
    // state where the download is active and has a known target) is the state
    // that most external users are interested in.
    case IN_PROGRESS_INTERNAL:
        return IN_PROGRESS;
    case COMPLETING_INTERNAL:
        return IN_PROGRESS;
    case COMPLETE_INTERNAL:
        return COMPLETE;
    case CANCELLED_INTERNAL:
        return CANCELLED;
    case INTERRUPTED_INTERNAL:
        return INTERRUPTED;
    case RESUMING_INTERNAL:
        return IN_PROGRESS;
    case MAX_DOWNLOAD_INTERNAL_STATE:
        break;
    }
    NOTREACHED();
    return MAX_DOWNLOAD_STATE;
}

// static
DownloadItemImpl::DownloadInternalState
DownloadItemImpl::ExternalToInternalState(
    DownloadState external_state)
{
    switch (external_state) {
    case IN_PROGRESS:
        return IN_PROGRESS_INTERNAL;
    case COMPLETE:
        return COMPLETE_INTERNAL;
    case CANCELLED:
        return CANCELLED_INTERNAL;
    case INTERRUPTED:
        return INTERRUPTED_INTERNAL;
    default:
        NOTREACHED();
    }
    return MAX_DOWNLOAD_INTERNAL_STATE;
}

// static
bool DownloadItemImpl::IsValidSavePackageStateTransition(
    DownloadInternalState from,
    DownloadInternalState to)
{
#if DCHECK_IS_ON()
    switch (from) {
    case INITIAL_INTERNAL:
    case TARGET_PENDING_INTERNAL:
    case INTERRUPTED_TARGET_PENDING_INTERNAL:
    case TARGET_RESOLVED_INTERNAL:
    case COMPLETING_INTERNAL:
    case COMPLETE_INTERNAL:
    case INTERRUPTED_INTERNAL:
    case RESUMING_INTERNAL:
    case CANCELLED_INTERNAL:
        return false;

    case IN_PROGRESS_INTERNAL:
        return to == CANCELLED_INTERNAL || to == COMPLETE_INTERNAL;

    case MAX_DOWNLOAD_INTERNAL_STATE:
        NOTREACHED();
    }
    return false;
#else
    return true;
#endif
}

// static
bool DownloadItemImpl::IsValidStateTransition(DownloadInternalState from,
    DownloadInternalState to)
{
#if DCHECK_IS_ON()
    switch (from) {
    case INITIAL_INTERNAL:
        return to == TARGET_PENDING_INTERNAL || to == INTERRUPTED_TARGET_PENDING_INTERNAL;

    case TARGET_PENDING_INTERNAL:
        return to == INTERRUPTED_TARGET_PENDING_INTERNAL || to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL;

    case INTERRUPTED_TARGET_PENDING_INTERNAL:
        return to == INTERRUPTED_INTERNAL || to == CANCELLED_INTERNAL;

    case TARGET_RESOLVED_INTERNAL:
        return to == IN_PROGRESS_INTERNAL || to == INTERRUPTED_INTERNAL || to == CANCELLED_INTERNAL;

    case IN_PROGRESS_INTERNAL:
        return to == COMPLETING_INTERNAL || to == CANCELLED_INTERNAL || to == INTERRUPTED_INTERNAL;

    case COMPLETING_INTERNAL:
        return to == COMPLETE_INTERNAL;

    case COMPLETE_INTERNAL:
        return false;

    case INTERRUPTED_INTERNAL:
        return to == RESUMING_INTERNAL || to == CANCELLED_INTERNAL;

    case RESUMING_INTERNAL:
        return to == TARGET_PENDING_INTERNAL || to == INTERRUPTED_TARGET_PENDING_INTERNAL || to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL;

    case CANCELLED_INTERNAL:
        return false;

    case MAX_DOWNLOAD_INTERNAL_STATE:
        NOTREACHED();
    }
    return false;
#else
    return true;
#endif // DCHECK_IS_ON()
}

const char* DownloadItemImpl::DebugDownloadStateString(
    DownloadInternalState state)
{
    switch (state) {
    case INITIAL_INTERNAL:
        return "INITIAL";
    case TARGET_PENDING_INTERNAL:
        return "TARGET_PENDING";
    case INTERRUPTED_TARGET_PENDING_INTERNAL:
        return "INTERRUPTED_TARGET_PENDING";
    case TARGET_RESOLVED_INTERNAL:
        return "TARGET_RESOLVED";
    case IN_PROGRESS_INTERNAL:
        return "IN_PROGRESS";
    case COMPLETING_INTERNAL:
        return "COMPLETING";
    case COMPLETE_INTERNAL:
        return "COMPLETE";
    case CANCELLED_INTERNAL:
        return "CANCELLED";
    case INTERRUPTED_INTERNAL:
        return "INTERRUPTED";
    case RESUMING_INTERNAL:
        return "RESUMING";
    case MAX_DOWNLOAD_INTERNAL_STATE:
        break;
    };
    NOTREACHED() << "Unknown download state " << state;
    return "unknown";
}

const char* DownloadItemImpl::DebugResumeModeString(ResumeMode mode)
{
    switch (mode) {
    case RESUME_MODE_INVALID:
        return "INVALID";
    case RESUME_MODE_IMMEDIATE_CONTINUE:
        return "IMMEDIATE_CONTINUE";
    case RESUME_MODE_IMMEDIATE_RESTART:
        return "IMMEDIATE_RESTART";
    case RESUME_MODE_USER_CONTINUE:
        return "USER_CONTINUE";
    case RESUME_MODE_USER_RESTART:
        return "USER_RESTART";
    }
    NOTREACHED() << "Unknown resume mode " << mode;
    return "unknown";
}

} // namespace content
